﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace LuDo.PitEditor
{
    class BBCodeWorker
    {
        private string plainText;
        internal string PlainText
        {
            get
            {
                return plainText;
            }
        }
        private List<RichFormat> FormatList { get; set; }
        private RichTextBoxLinks.RichTextBoxEx RTB { get; set; }
        private Redattore red { get; set; }

        /// <summary>
        /// Crea un Worker vuoto: niente testo e niente formattazioni
        /// </summary>
        internal BBCodeWorker(RichTextBoxLinks.RichTextBoxEx box)
        {
            this.plainText = "";
            this.RTB = box;
            InitRtb();
            //this.RTB.Protected += new EventHandler(RTB_Protected);
            this.FormatList = new List<RichFormat>();
        }

        /// <summary>
        /// Inizializza il Worker a partire da un testo BBCode con tag
        /// </summary>
        /// <param name="BBcText">Il testo con tag</param>
        internal void Initialize(string BBcText)
        {
            // -------------------------------------------------
            // LETTURA DI UN TESTO E CREAZIONE DELLA FORMATLIST
            // -------------------------------------------------

            this.FormatList.Clear();
            List<BBTag> TagList = new List<BBTag>();

            //Scorri il testo dall'inizio alla fine
            int pos = 0;        //Posizione del parsing reale nel testo BBCode
            int posX = 0;       //Posizione del parsing relativa (al netto dei tag)
            int StartPoint = 0; //Posizione relativa inizio del tag corrente
            int EndPoint = 0;   //Posizione relativa fine del tag corrente

            //BBcText = BBcText.Replace("\r\n", "\r");
            string TestoFinale = "";
            while (pos < BBcText.Length)
            {
                //Individua la stringa
                int caratteriResidui = BBcText.Length - pos;
                string testoResiduo = BBcText.Substring(pos);

                //Verifica se ci si trova di fronte ad un tag
                BBTag tag = BBTag.GetTag(testoResiduo);
                if (tag == null)
                {
                    TestoFinale += testoResiduo[0];
                    pos++;
                    posX++;
                }
                else if (tag.Type == BBTag.TagType.ListItem)
                //Il tag ListItem [*] è l'unico che non ha un tag di chiusura.
                {
                    //Aggiungi elemento a format list
                    pos = pos + tag.TagText.Length;          //<--- METTERE A POSTO QUI
                }
                else
                {
                    if (tag.State == BBTag.TagState.Opening)
                    {   //TROVATO TAG DI APERTURA         
                        StartPoint = posX;
                        TagList.Add(tag);
                    }
                    else
                    {   //TROVATO TAG DI CHIUSURA         
                        EndPoint = posX;
                        BBTag LastTag = TagList.Last();
                        if (LastTag.Type != tag.Type)
                        {
                            throw new ApplicationException("Tag non formattati correttamente.");
                        }
                        //else if (LastTag.Type == BBTag.TagType.CodeSize)
                        //{
                        //    try
                        //    {
                        //        BBTag PreviousTag = TagList[TagList.Count - 2];
                        //        if (PreviousTag.Type != BBTag.TagType.Code)
                        //        {
                        //            throw new ApplicationException();
                        //        }
                        //        TagList.Remove(LastTag);
                        //    }
                        //    catch
                        //    {
                        //        throw new ApplicationException("Tag non formattati correttamente.");
                        //    }
                        //}
                        else
                        {
                            RichFormat.FormatType type = tag.Type == BBTag.TagType.Bold ? RichFormat.FormatType.Bold :
                                tag.Type == BBTag.TagType.Italic ? RichFormat.FormatType.Italic :
                                tag.Type == BBTag.TagType.Underline ? RichFormat.FormatType.Underline :
                                tag.Type == BBTag.TagType.Strikethrough ? RichFormat.FormatType.Strikethrough :
                                tag.Type == BBTag.TagType.SuperScript ? RichFormat.FormatType.Superscript :
                                tag.Type == BBTag.TagType.Code ? RichFormat.FormatType.Code :
                                tag.Type == BBTag.TagType.Link ? RichFormat.FormatType.Link :
                                tag.Type == BBTag.TagType.List ? RichFormat.FormatType.List :
                                RichFormat.FormatType.Bold;
                            RichFormat rf = new RichFormat(type, StartPoint, EndPoint - StartPoint);
                            rf.URL = LastTag.URL;
                            rf.TargetLink = LastTag.TargetLink;
                            FormatList.Add(rf);
                            TagList.Remove(LastTag);
                        }
                    }
                    pos = pos + tag.TagText.Length;
                }
            }
            this.plainText = TestoFinale;
        }

        /// <summary>
        /// A partire dal Worker, aggiorna con testo formattato un dato RichTextBoxEx
        /// </summary>
        /// <param name="box">Il controllo RichTextBoxEx da aggiornare</param>
        internal void UpdateRichTextBox()
        {
            RTB.Hide();
            RTB.Clear();
            RTB.Text = this.PlainText;
            foreach (RichFormat rf in FormatList)
            {
                int RTBStart = GetRTBPosition(rf.StartPoint);
                int RTBEnd = GetRTBPosition(rf.EndPoint);
                int Length = RTBEnd - RTBStart + 1;
                RTB.Select(RTBStart, Length);
                if (rf.Type != RichFormat.FormatType.Link)
                {
                    bool formatted = SetFormat(rf.Type, false);
                }
                else
                {
                    bool formatted = SetFormatLink(rf.URL, rf.TargetLink, false);
                }
            }
            RTB.Show();
        }

        #region Gestione modifiche fatte sul RTB
        //Gestione inserimenti
        internal void InsertChar(char ch)
        {
            InsertChar(RTB.SelectionStart, ch);
        }
        internal void InsertChar(int Position, char ch)
        {
            if (RTB.SelectionLength > 0)
            {
                //Gestione inserimento carattere con più caratteri selezionati
                HandleCancel();
            }

            bool IsEnter = ch == '\r';
            int Line = this.RTB.GetLineFromCharIndex(Position);
            bool IsBeginningOfRow = this.RTB.GetFirstCharIndexFromLine(Line) == Position;
            int pos = GetPlainTextPosition(IsEnter ? Position - 1 : Position);
            string CurrentPosText = this.PlainText.Substring(pos);
            string Text = IsEnter ? "\r\n" : ch.ToString();


            IsEnter = Text == "\r\n";
            //Aggiornamento FormatList
            foreach (RichFormat rf in FormatList)
            {
                bool FormatIsBefore = rf.IsBefore(pos);
                bool FormatStartsHere = rf.IsFirstPosition(pos);
                bool FormatIsAround = rf.IsAround(pos);
                bool AfterFormat = rf.IsNextPosition(pos);
                bool FormatIsAfter = rf.IsAfter(pos);
                bool IsLink = rf.Type == RichFormat.FormatType.Link;

                if (FormatIsAfter)
                {
                    //Se la formattazione è successiva, spostala avanti
                    rf.StartPoint += Text.Length;
                }
                else if (FormatStartsHere && (IsLink || !IsBeginningOfRow))
                {
                    //Se si tratta della prima posizione di un link o di una formattazione non all'inizio della riga, spostala avanti
                    rf.StartPoint += Text.Length;
                }
                else if (IsLink)
                {
                    //All'interno di un link non aggiungere
                    if (FormatIsAround)
                    {
                        string message = "Impossibile inserire testo all'interno di un link. Rimuovere la formattazione e riprovare.";
                        QuickInfo qi = new QuickInfo(RTB, message);
                        throw new ApplicationException();
                    }
                }
                //Se all'interno di un formato, oppure il primo carattere successivo, oppure nel primo carattere se all'inizio della riga, allunga la form.
                else if ((FormatIsAround || AfterFormat) || (FormatStartsHere && IsBeginningOfRow))
                {
                    rf.Length += Text.Length;
                }
            }

            //Aggiornamento PlainText
            string Before = this.PlainText.Substring(0, pos);
            string After = this.PlainText.Substring(pos);
            this.plainText = Before + Text + After;
        }
        internal void InsertString(string newText)
        {
            char[] chararray = newText.ToCharArray();
            foreach (char c in chararray.Reverse())
            {
                InsertChar(c);
            }
        }
        //Gestione cancellazioni
        internal void HandleBackSpace()
        {
            //Determinazione posizione e lunghezze
            if (RTB.SelectionLength == 0)
            {
                int StartSel = GetPlainTextPosition(RTB.SelectionStart - 1);
                DeleteAtPlainPos(StartSel);
            }
            else
            {
                //Gestione backspace con più caratteri selezionati = Gestione Cancel in analoga situazione
                int StartSel = GetPlainTextPosition(RTB.SelectionStart);
                int EndSel = GetPlainTextPosition(RTB.SelectionStart + RTB.SelectionLength - 1);

                //Verifica eccezione fine link
                var EndLinkList = IsAListEnd(EndSel);
                bool IsLink = EndLinkList.Count() > 0;
                if (IsLink)
                {
                    string message = "Per cancellare un link è necessario selezionare anche il carattere successivo.";
                    QuickInfo qi = new QuickInfo(RTB, message);
                    throw new ApplicationException();
                }

                for (int i = EndSel; i >= StartSel; i--)
                {
                    string before = PlainText;
                    string CharToBeDeleted = PlainText.Substring(i, 1);
                    DeleteAtPlainPos(i);
                    string after = PlainText;
                }
            }
            CleanOutFormatting();
        }
        internal void HandleCancel()
        {
            if (RTB.SelectionLength == 0)
            {
                int StartSel = GetPlainTextPosition(RTB.SelectionStart);
                DeleteAtPlainPos(StartSel);
            }
            else
            {
                //Gestione Cancel con più caratteri selezionati
                int StartSel = GetPlainTextPosition(RTB.SelectionStart);
                int EndSel = GetPlainTextPosition(RTB.SelectionStart + RTB.SelectionLength - 1);

                //Verifica eccezione fine link
                var EndLinkList = IsAListEnd(EndSel);
                bool IsLink = EndLinkList.Count() > 0;
                if (IsLink)
                {
                    string message = "Per cancellare un link è necessario selezionare anche il carattere successivo.";
                    QuickInfo qi = new QuickInfo(RTB, message);
                    throw new ApplicationException();
                }

                for (int i = EndSel; i >= StartSel; i--)
                {
                    string before = PlainText;
                    string CharToBeDeleted = PlainText.Substring(i, 1);
                    DeleteAtPlainPos(i);
                    string after = PlainText;
                }
            }
            CleanOutFormatting();
        }
        private void DeleteAtPlainPos(int PlainPosition)
        {
            if (-1 < PlainPosition && PlainPosition < this.PlainText.Length)
            {
                int InizioCancellazione = PlainPosition;

                string Middle = this.PlainText.Substring(InizioCancellazione, 1);
                bool IsEnter = Middle == "\r";

                foreach (RichFormat rf in FormatList)
                {
                    bool IsAfter = rf.IsAfter(InizioCancellazione);
                    bool ContainsDelete = rf.IsAround(InizioCancellazione);
                    bool IsDelete = rf.IsEqualToSelection(InizioCancellazione, 1);
                    bool IsBefore = rf.IsBefore(InizioCancellazione);
                    bool IsLink = rf.Type == RichFormat.FormatType.Link;

                    if (IsAfter)
                    {
                        rf.StartPoint = IsEnter ? rf.StartPoint - 2 : rf.StartPoint - 1;
                    }
                    else if (ContainsDelete || IsDelete)
                    {
                        rf.Length -= IsEnter ? 2 : 1;
                    }
                }

                //Aggiornamento PlainText
                string Before = this.PlainText.Substring(0, InizioCancellazione);
                string After = this.PlainText.Substring(IsEnter ? InizioCancellazione + 2 : InizioCancellazione + 1);
                this.plainText = Before + After;
            }
        }
        //Gestione formati
        internal bool SetFormat(RichFormat.FormatType Type, bool UpdateFormatList)
        {
            if (RTB.SelectionLength > 0)
            {
                bool LastNewLine = this.RTB.SelectedText[this.RTB.SelectedText.Length - 1] == '\n';
                int Start = GetPlainTextPosition(RTB.SelectionStart);
                int End = GetPlainTextPosition(LastNewLine ? RTB.SelectionStart + RTB.SelectionLength - 1 : RTB.SelectionStart + RTB.SelectionLength);
                int Length = End - Start;

                //if (LastNewLine)
                //{
                //    Length -= 2;
                //}
                bool Overlying = (from RichFormat rf in FormatList
                                  where rf.IsOverlyingSelection(Start, Length)
                                  select rf).Count() > 0;
                if (UpdateFormatList && Overlying)
                {
                    string message = "La formattazione richiesta si sovrapporrebbe ad un'altra. Impossibile procedere.";
                    QuickInfo qi = new QuickInfo(RTB, message);
                    return false;
                }
                else
                {
                    if (Type == RichFormat.FormatType.Bold)
                    {
                        Font CurFont = RTB.SelectionFont;
                        RTB.SelectionFont = new Font(CurFont.FontFamily, CurFont.Size, FontStyle.Bold);

                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(Type, Start, Length);
                            FormatList.Add(rf);
                        }
                    }
                    else if (Type == RichFormat.FormatType.Underline)
                    {
                        Font CurFont = RTB.SelectionFont;
                        RTB.SelectionFont = new Font(CurFont.FontFamily, CurFont.Size, FontStyle.Underline);

                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(Type, Start, Length);
                            FormatList.Add(rf);
                        }
                    }
                    else if (Type == RichFormat.FormatType.Italic)
                    {
                        Font CurFont = RTB.SelectionFont;
                        RTB.SelectionFont = new Font(CurFont.FontFamily, CurFont.Size, FontStyle.Italic);

                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(Type, Start, Length);
                            FormatList.Add(rf);
                        }
                    }
                    else if (Type == RichFormat.FormatType.Strikethrough)
                    {
                        Font CurFont = RTB.SelectionFont;
                        RTB.SelectionFont = new Font(CurFont.FontFamily, CurFont.Size, FontStyle.Strikeout);

                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(Type, Start, Length);
                            FormatList.Add(rf);
                        }
                    }
                    else if (Type == RichFormat.FormatType.Superscript)
                    {
                        RTB.SelectionCharOffset = 4;

                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(Type, Start, Length);
                            FormatList.Add(rf);
                        }
                    }
                    else if (Type == RichFormat.FormatType.Code)
                    {
                        RTB.SelectionFont = new Font(FontFamily.GenericMonospace, 10, FontStyle.Regular);
                        RTB.SelectionBackColor = Color.Azure;

                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(Type, Start, Length);
                            FormatList.Add(rf);
                        }
                    }
                    else if (Type == RichFormat.FormatType.List)
                    {
                        //Manca la gestione delle liste ordinate e alfabetiche.
                        char[] array = RTB.SelectedText.ToCharArray();
                        int off = 0;
                        int p = 0;
                        while (array[p++] == '\n')
                        {
                            off++;
                        }
                        string text = RTB.SelectedText;

                        int start = RTB.SelectionStart;
                        int length = RTB.SelectionLength;
                        RTB.Select(start + off, length - off);
                        RTB.SelectionBullet = true;
                        RTB.SelectionIndent = 5;
                        RTB.SelectionHangingIndent = 10;

                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(Type, Start, Length);
                            FormatList.Add(rf);
                        }
                    }
                    //else if (Type == RichFormat.FormatType.NList)
                    //{
                    //    char[] array = RTB.SelectedText.ToCharArray();
                    //    int off = 0;
                    //    int p = 0;
                    //    while (array[p++] == '\n')
                    //    {
                    //        off++;
                    //    }
                    //    string text = RTB.SelectedText;

                    //    int start = RTB.SelectionStart;
                    //    int length = RTB.SelectionLength;
                    //    RTB.Select(start + off, length - off);
                    //    RTB.SelectionNumberedList = true;
                    //    //RTB.SelectionBullet = true;
                    //    //RTB.SelectionIndent = 5;
                    //    //RTB.SelectionHangingIndent = 10;

                    //    if (UpdateFormatList)
                    //    {
                    //        RichFormat rf = new RichFormat(Type, Start, Length);
                    //        FormatList.Add(rf);
                    //    }
                    //}
                    else if (Type == RichFormat.FormatType.Link)
                    {
                        throw new ArgumentException("Impossibile formattare un link con il metodo richiesto", "Type");
                    }
                    return true;
                }
            }
            else
            {
                return false;
            }
        }
        internal bool SetFormatLink(string URL, string TargetLink, bool UpdateFormatList)
        {
            if (RTB.SelectionLength > 0)
            {
                string selected = this.RTB.SelectedText;
                bool ContainsNewLine = selected.Substring(0, selected.Length - 2).Contains('\n');
                if (ContainsNewLine)
                {
                    string message = "Impossibile formattare a link testi che contengono carattere 'a capo'.";
                    QuickInfo qi = new QuickInfo(RTB, message);
                    return false;
                }
                else
                {
                    bool LastNewLine = selected[selected.Length - 1] == '\n';
                    int Start = GetPlainTextPosition(RTB.SelectionStart);
                    int End = GetPlainTextPosition(LastNewLine ? RTB.SelectionStart + RTB.SelectionLength - 1 : RTB.SelectionStart + RTB.SelectionLength);
                    int Length = End - Start;

                    bool Overlying = (from RichFormat rf in FormatList
                                      where rf.IsOverlyingSelection(Start, Length)
                                      select rf).Count() > 0;
                    if (UpdateFormatList && Overlying)
                    {
                        string message = "Il link richiesto si sovrapporrebbe ad un'altra formattazione. Impossibile procedere.";
                        QuickInfo qi = new QuickInfo(RTB, message);
                        return false;
                    }
                    else
                    {
                        //Crea un link esterno
                        if (LastNewLine)
                            RTB.SelectionLength -= 1;
                        RTB.InsertLink(RTB.SelectedText, URL, RTB.SelectionStart);
                        if (UpdateFormatList)
                        {
                            RichFormat rf = new RichFormat(RichFormat.FormatType.Link, Start, Length);
                            rf.URL = URL;
                            rf.TargetLink = TargetLink;
                            FormatList.Add(rf);
                            CleanOutFormatting();
                        }
                        return true;
                    }
                }
            }
            else
            {
                return false;
            }
        }
        internal void RemoveFormat()
        {
            bool LastNewLine = this.RTB.SelectedText.Length > 0 ? this.RTB.SelectedText[this.RTB.SelectedText.Length - 1] == '\n' : false;
            int Start = GetPlainTextPosition(RTB.SelectionStart);
            int End = GetPlainTextPosition(LastNewLine ? RTB.SelectionStart + RTB.SelectionLength - 1 : RTB.SelectionStart + RTB.SelectionLength);
            int Length = End - Start;

            foreach (RichFormat rf in FormatList)
            {
                bool IsAround = rf.IsAround(Start);
                bool IsOverlying = rf.IsOverlyingSelection(Start, Length);
                if (IsAround || IsOverlying)
                {
                    rf.Length = 0;
                }
            }
            CleanOutFormatting();
            UpdateRichTextBox();
        }
        private void CleanOutFormatting()
        {
            //NOTA: Non modificare direttamente il RTB da questo metodo.
            //      Interferisce con l'aggiornamento del controllo.

            //Cancellazione formattazioni a lunghezza zero.
            for (int i = FormatList.Count - 1; i >= 0; i--)
            {
                if (FormatList[i].Length == 0)
                {
                    //RemoveFormat(FormatList[i].StartPoint, 0);
                    //RemoveFormatAt(FormatList[i].StartPoint);
                    FormatList.RemoveAt(i);
                }
            }

            //Fusione di formattazioni attaccate.
            foreach (RichFormat rf in FormatList)
            {
                foreach (RichFormat rf2 in FormatList)
                {
                    if (rf.Type == rf2.Type && rf.EndPoint == rf2.StartPoint - 1)
                    {
                        rf.Length += rf2.Length;
                        rf2.Length = 0;
                    }
                }
            }
            //Cancellazione formattazioni a lunghezza zero.
            for (int i = FormatList.Count - 1; i >= 0; i--)
            {
                if (FormatList[i].Length == 0)
                {
                    FormatList.RemoveAt(i);
                }
            }
            //Ordinamento formattazioni
            FormatList = FormatList.OrderBy(c => c.StartPoint).ToList();
        }
        #endregion

        #region Utils
        /// <summary>
        /// A partire dal Worker, genera il testo BBCode con tag
        /// </summary>
        /// <returns>Il testo BBCode con tag</returns>
        internal string GetBBCode()
        {
            // -------------------------------------------------
            // UTILIZZO DELLA FORMATLIST PER GENERARE IL TESTO
            // -------------------------------------------------
            const string LISTITEMTAG = "[*]";

            string code = PlainText;
            foreach (RichFormat rf in FormatList)
            {
                string tag = "";
                string closingtag = "";
                switch (rf.Type)
                {
                    case RichFormat.FormatType.Bold:
                        tag = "[b]";
                        closingtag = "[/b]";
                        break;
                    case RichFormat.FormatType.Underline:
                        tag = "[u]";
                        closingtag = "[/u]";
                        break;
                    case RichFormat.FormatType.Italic:
                        tag = "[i]";
                        closingtag = "[/i]";
                        break;
                    case RichFormat.FormatType.Strikethrough:
                        tag = "[s]";
                        closingtag = "[/s]";
                        break;
                    case RichFormat.FormatType.Superscript:
                        tag = "[sup]";
                        closingtag = "[/sup]";
                        break;
                    case RichFormat.FormatType.Code:
                        tag = "[code]";
                        closingtag = "[/code]";
                        break;
                    case RichFormat.FormatType.Link:
                        tag = String.Format("[url={0} t={1}]", rf.URL, rf.TargetLink);
                        closingtag = "[/url]";
                        break;
                    case RichFormat.FormatType.List:
                        if (rf.ListType == RichFormat.ListTypes.None || rf.ListType == RichFormat.ListTypes.Unsorted)
                        {
                            tag = "[list]";
                        }
                        else if (rf.ListType == RichFormat.ListTypes.Sorted)
                        {
                            tag = "[list=1]";
                        }
                        else if (rf.ListType == RichFormat.ListTypes.Unsorted)
                        {
                            tag = "[list=a]";
                        }
                        closingtag = "[/list]";
                        break;
                }
                int newPos = GetTaggedPosition(rf.StartPoint, code);
                int closingPos = GetTaggedPosition(rf.StartPoint + rf.Length, code);
                int length = closingPos - newPos;
                string A1 = code.Substring(0, newPos);
                string A2 = "";
                //Aggiunta degli tag elemento di lista del BBCode
                if (rf.Type == RichFormat.FormatType.List)
                {
                    string NewLine = "\r\n";
                    string TaggedNewLine = NewLine + LISTITEMTAG;
                    A2 = LISTITEMTAG + code.Substring(newPos, length).Replace(NewLine, TaggedNewLine);
                }
                else
                {
                    A2 = code.Substring(newPos, length);
                }
                string A3 = code.Substring(closingPos);
                code = A1 + tag + A2 + closingtag + A3;
            }
            return code;
        }
        internal string GetSelectedText(bool IncludeHiddenText)
        {
            string text = "";
            if (IncludeHiddenText)
            {
                text = RTB.SelectedText;
            }
            else
            {
                text = GetSelectedText();
            }
            return text;
        }
        private string GetSelectedText()
        {
            int start = RTB.SelectionStart;
            int length = RTB.SelectionLength;
            int PlainStart = GetPlainTextPosition(start);
            int PlainEnd = GetPlainTextPosition(start + length);
            int PlainLength = PlainEnd - PlainStart;
            return this.PlainText.Substring(PlainStart, PlainLength);
        }
        private int GetSelectionHiddenTextLength()
        {
            int start = RTB.SelectionStart;
            int length = RTB.SelectionLength;
            int PlainStart = GetPlainTextPosition(start);
            int PlainEnd = GetPlainTextPosition(start + length);
            int PlainLength = PlainEnd - PlainStart;
            var query = from RichFormat rf in FormatList
                        where rf.Type == RichFormat.FormatType.Link &&
                        PlainStart <= rf.EndPoint && rf.EndPoint <= PlainEnd
                        select rf;
            int htl = query.Sum(c => c.URL.Length) + 1;
            return htl;
        }
        private int GetURLLengthAt(int PlainPos)
        {
            var query = from RichFormat rf in FormatList
                        where rf.Type == RichFormat.FormatType.Link &&
                        rf.StartPoint <= PlainPos && PlainPos <= rf.EndPoint
                        select rf;
            int htl = query.Count() == 0 ? 0 : query.Sum(c => c.URL.Length) + 1;
            return htl;
        }
        /// <summary>
        /// A partire dalla posizione sul PlainText, restituisce la posizione nel testo completo di tag
        /// </summary>
        /// <param name="PlainPos">Posizione voluta nel PlainText</param>
        /// <param name="TaggedText">Testo gemello al PlainText completo di tag</param>
        /// <returns></returns>
        private int GetTaggedPosition(int PlainPos, string TaggedText)
        {
            int pos = 0;    //Posizione di parsing testo con tag
            int posX = 0;   //Posizione di parsing testo esclusi tag
            //bool Counting = true;
            while (posX < PlainPos)
            {
                //char proxCarattere = TaggedText[pos];
                string proxTesto = TaggedText.Substring(pos);
                BBTag tag = BBTag.GetTag(proxTesto);
                if (tag != null)
                {
                    //posX++;
                    pos += tag.TagText.Length;
                }
                else
                {
                    posX++;
                    pos++;
                }
            }
            return pos;
        }
        /// <summary>
        /// A partire dalla posizione sul PlainText, restituisce la posizione nel RichTextBoxEx
        /// </summary>
        /// <param name="PlainTextPosition">Posizione voluta nel PlainText</param>
        /// <returns></returns>
        private int GetRTBPosition(int PlainTextPosition)
        {
            int pos = 0;    //Contatore Plain
            int posX = 0;   //Contatore RTB    
            while (pos < PlainTextPosition)
            {
                bool NextCharSlashR = this.PlainText[pos] == '\r';
                var EndLinkList = from RichFormat rf in FormatList
                                  where rf.Type == RichFormat.FormatType.Link
                                  && rf.EndPoint == pos
                                  select rf;
                bool IsLink = EndLinkList.Count() > 0;
                if (!NextCharSlashR)
                {
                    if (IsLink)
                    {
                        RichFormat rf = EndLinkList.First();
                        posX = posX + 1 + rf.URL.Length + 1;
                    }
                    else
                    {
                        posX++;
                    }
                }
                pos++;
            }
            return posX;
        }
        /// <summary>
        /// A partire dalla posizione indicata su un RichTextBoxEx, restituisce la posizione nel PlainText
        /// </summary>
        /// <param name="RTBPosition">Posizione sul RichTextBoxEx</param>
        /// <returns>Posizione nel PlainText</returns>
        private int GetPlainTextPosition(int RTBPosition)
        {
            int pos = 0;    //Contatore Plain
            int posX = 0;   //Contatore RTB
            while (posX < RTBPosition)
            {
                bool NextCharSlashR = this.PlainText[pos] == '\r';
                if (!NextCharSlashR)
                {
                    var EndLinkList = IsAListEnd(pos);
                    bool IsLink = EndLinkList.Count() > 0;
                    if (IsLink)
                    {
                        try
                        {
                            RichFormat rf = EndLinkList.First();

                            //Controllo se la posizione RTBPosition ricade dentro l'hidden Text del
                            //RichTextBoxEx che indica l'URL del link ma che non è visibile in chiaro.
                            int hiddenTextLength = rf.URL.Length;
                            if (RTBPosition > posX + hiddenTextLength)
                            {
                                posX += rf.URL.Length + 1;
                                if (posX == RTBPosition)
                                {
                                    pos--;
                                }
                            }
                        }
                        catch
                        {
                        }
                    }
                    posX++;
                }
                pos++;
            }
            return pos;
        }
        private IEnumerable<RichFormat> IsAListEnd(int Position)
        {
            var query = from RichFormat rf in FormatList
                        where rf.Type == RichFormat.FormatType.Link
                        && rf.EndPoint == Position
                        select rf;
            return query;
        }
        internal string GetLinks()
        {
            StringBuilder sb = new StringBuilder();
            foreach (RichFormat rf in FormatList)
            {
                if (rf.Type == RichFormat.FormatType.Link)
                {
                    string cs = sb.ToString();
                    if (!cs.Contains(rf.URL))
                    {
                        sb.AppendLine(rf.URL);
                    }
                }
            }
            return sb.ToString();
        }
        #endregion

        #region SpellChecking
        private void InitRtb()
        {
            red = new Redattore();
            this.RTB.MyPaint += OnPaint;
            this.RTB.MouseMove += new MouseEventHandler(RTB_MouseMove);
            //r += new EventHandler(RTB_MouseHover);
        }

        internal int SpellCheck(string Titolo)
        {
            red.PulisciSegnalazioni();
            red.ControllaTitolo(Titolo);
            red.ControllaArticolo(this.PlainText);
            red.ControllaLink(this.GetLinks());
            foreach (var segn in red.Segnalazioni)
            {
                if (segn.Section == SegnalazioniRedattore.Sections.Articolo)
                {
                    segn.RTBPosition = GetRTBPosition(segn.Position);
                    int end = GetRTBPosition(segn.Position + segn.Length);
                    segn.RTBLength = end - segn.RTBPosition;
                }
            }
            int numb = red.Segnalazioni.Where(c => c.Section == SegnalazioniRedattore.Sections.Articolo).Count();
            this.RTB.Invalidate();
            return numb;
        }
        internal void SpellCheckClear()
        {
            if (red.Segnalazioni.Count > 0)
            {
                red.PulisciSegnalazioni();
                this.RTB.Invalidate();
                SegnalazioniTip.RemoveAll();
                LastShownPosition = new Point(-500, -500);
            }
        }
        void OnPaint(object sender, PaintEventArgs e)
        {

            //base.OnPaint(e);
            if (red.Segnalazioni != null && red.Segnalazioni.Count > 0)
            {
                var query = red.Segnalazioni.Where(c => c.Section == SegnalazioniRedattore.Sections.Articolo);
                foreach (LuDo.PitEditor.SegnalazioniRedattore segn in query)
                {
                    //Drawing of a zig zag underline
                    int startLine = this.RTB.GetLineFromCharIndex(segn.RTBPosition);
                    int endLine = this.RTB.GetLineFromCharIndex(segn.RTBPosition + segn.RTBLength);

                    int FirstChar = segn.RTBPosition;
                    int EndChar = segn.RTBPosition + segn.RTBLength;
                    int LastLine = this.RTB.GetLineFromCharIndex(EndChar);

                    //Loop starts here
                    while (FirstChar < EndChar)
                    {
                        int CurrentLine = this.RTB.GetLineFromCharIndex(FirstChar);
                        int LastCharOfLine = EndChar;
                        if (CurrentLine < LastLine)
                        {
                            LastCharOfLine = this.RTB.GetFirstCharIndexFromLine(CurrentLine + 1) - 1;
                        }

                        if (EndChar > LastCharOfLine)
                        {
                            //Draw underline for the whole line, from FirstChar to LastCharOfLine
                            UnderlineThis(e, FirstChar, LastCharOfLine);
                            //Change FirstChar to EndChar+1
                            FirstChar = LastCharOfLine + 1;
                            //Restart loop
                        }
                        else
                        {
                            //Draw underline from FirstChar to EndChar
                            UnderlineThis(e, FirstChar, EndChar);
                            //Change FirstChar to EndChar+1
                            FirstChar = EndChar + 1;
                            //Finished
                        }
                    }
                }
            }
        }

        private void UnderlineThis(PaintEventArgs e, int start, int end)
        {
            const int HSTEP = 2;
            const int VSTEP = 2;

            Point startPoint = this.RTB.GetPositionFromCharIndex(start);
            int VerticalOffset = Convert.ToInt32(this.RTB.Font.GetHeight() - 2);
            startPoint.Y += VerticalOffset;
            Point endPoint = this.RTB.GetPositionFromCharIndex(end);
            endPoint.Y += VerticalOffset;
            int maxY = startPoint.Y;
            int minY = startPoint.Y + VSTEP;

            Graphics graphics = e.Graphics;
            System.Drawing.Pen myPen = new System.Drawing.Pen(Color.Red, 1);
            int currY = minY;
            Point last = startPoint;
            for (int i = startPoint.X; i < endPoint.X; i += HSTEP)
            {
                Point newPoint = new Point(i + HSTEP, currY);
                graphics.DrawLine(myPen, last, newPoint);
                currY = currY == minY ? maxY : minY;
                last = newPoint;
            }
            //graphics.DrawLine(myPen, startPoint, endPoint);  
            myPen.Dispose();
        }

        private ToolTip SegnalazioniTip { get; set; }

        private DateTime LastShownTime { get; set; }
        private Point LastShownPosition { get; set; }

        void RTB_MouseMove(object sender, MouseEventArgs e)
        {
            if (red.Segnalazioni != null && red.Segnalazioni.Count > 0)
            {
                Point pos = this.RTB.PointToClient(Cursor.Position);
                int index = this.RTB.GetCharIndexFromPosition(pos);
                int PlainIndex = GetPlainTextPosition(index);
                var query = from SegnalazioniRedattore segn in red.Segnalazioni
                            where segn.Section == SegnalazioniRedattore.Sections.Articolo
                            && segn.Position <= PlainIndex && PlainIndex <= segn.Position + segn.Length
                            select segn;
                if (query.Count() > 0)
                {
                    SegnalazioniRedattore CurrentSegn = query.First();
                    if (SegnalazioniTip == null)
                    {
                        SegnalazioniTip = new ToolTip();
                        SegnalazioniTip.ShowAlways = true;
                        //SegnalazioniTip.BackColor = Color.LightYellow;
                        //SegnalazioniTip.ForeColor = Color.Red;
                    }
                    double distance = 0;
                    if (LastShownPosition == null || LastShownPosition.X < 0)
                    {
                        distance = 500;
                    }
                    else
                    {
                        TimeSpan span = DateTime.Now.Subtract(LastShownTime);
                        double cateto1 = pos.X - LastShownPosition.X;
                        double cateto2 = pos.Y - LastShownPosition.Y;
                        double sq1 = Math.Pow(cateto1, 2);
                        double sq2 = Math.Pow(cateto2, 2);
                        distance = Math.Sqrt(sq1 + sq2);
                    }

                    if (!SegnalazioniTip.Active || distance >= 10) // || span.TotalSeconds >= 3)
                    {
                        Point pos2 = new Point(pos.X + 5, pos.Y + 5);
                        string s = String.Format("{0}\r\n[{1}]", CurrentSegn.Description, CurrentSegn.Reference);
                        SegnalazioniTip.ToolTipIcon = ToolTipIcon.Warning;
                        SegnalazioniTip.Show(s, this.RTB, pos2);
                        //SegnalazioniTip.SetToolTip(this.RTB, text);
                        LastShownTime = DateTime.Now;
                        LastShownPosition = pos;
                    }
                }
                else
                {
                    if (SegnalazioniTip != null)
                    {
                        SegnalazioniTip.RemoveAll();
                        LastShownPosition = new Point(-500, -500);
                    }
                }
            }
        }
        #endregion
    }
}
